/**
 * 基于角色的访问控制（RBAC，Role-Based Access Control）守卫。
 * 在NestJS中，守卫（Guard）是一种用于保护路由的机制，它可以在请求到达控制器之前对其进行拦截和处理。
 * RBAC守卫的主要目的是根据用户的角色或权限来决定是否允许访问某个特定的路由或资源。
 */
import {CanActivate, ExecutionContext, Injectable} from '@nestjs/common';
import {Reflector} from '@nestjs/core';
import {FastifyRequest} from 'fastify';

import {BusinessException} from '~/common/exceptions/biz.exception';
import {ErrorEnum} from '~/constants/error-code.constant';
import {AuthService} from '~/modules/auth/auth.service';

import {ALLOW_ANON_KEY, PERMISSION_KEY, PUBLIC_KEY, Roles} from '../auth.constant';
import {UserRole} from '~/modules/system/role/role.constant';

@Injectable()
export class RbacGuard implements CanActivate {
  constructor(private reflector: Reflector, private authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise<any> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) return true;

    const request = context.switchToHttp().getRequest<FastifyRequest>();

    const {user} = request;

    if (!user) throw new BusinessException(ErrorEnum.INVALID_LOGIN);

    // allowAnon 是需要登录后可访问(无需权限), Public 则是无需登录也可访问.
    const allowAnon = this.reflector.get<boolean>(ALLOW_ANON_KEY, context.getHandler());
    if (allowAnon) return true;

    const payloadPermission = this.reflector.getAllAndOverride<string | string[]>(PERMISSION_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    // 控制器没有设置接口权限，则默认通过
    if (!payloadPermission) return true;

    // 管理员放开所有权限
    if (user.isSuperAdmin) {
      return true;
    }

    const allPermissions = await this.authService.getPermissions(user.uid);
    // (await this.authService.getPermissionsCache(user.uid)) ??
    // (await this.authService.getPermissions(user.uid));

    let canNext = false;

    // handle permission strings
    if (Array.isArray(payloadPermission)) {
      // 只要有一个权限满足即可
      canNext = payloadPermission.every((i) => allPermissions.includes(i));
    }

    if (typeof payloadPermission === 'string') canNext = allPermissions.includes(payloadPermission);

    if (!canNext) throw new BusinessException(ErrorEnum.NO_PERMISSION);

    return true;
  }
}
